/*
 * 实现长按按键，IP恢复出厂设置
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <mach/map.h>
#include <mach/gpio.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#include <linux/miscdevice.h>
#include <linux/input.h>
#include <linux/clk.h>
#include <plat/gpio-core.h>
#include <plat/gpio-cfg.h>
#include <plat/gpio-cfg-helpers.h>

#define DEVICE_NAME "buttons"

/* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */

#define IOCTL_GPIO_ON 1
#define IOCTL_GPIO_OFF 0

/*struct button_desc {
  int gpio;
  int number;
  char *name;
  };

  static struct button_desc buttons[] = {

  { S5PV210_GPH0(0), 0, "KEY" },

  };
*/

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);//生成一等待队列头
static struct timer_list button_timer;//内核定时器
static volatile int ev_press = 0;
static volatile int press_cnt[1]={0};
static int flag_button = 0;//判断按键是否释放
static int pin = 0;
static int num_button = 0;//按键时间计数

static irqreturn_t button_interrupt(int irq, void *dev_id)//中断处理程序
{
   //volatile int *press_cnt = (volatile int *)dev_id;
   //*press_cnt +=1;

    if(flag_button == 0){//判断按键是否按下，若未按下，将按键设为按下，设置定时器，加入定时器。若按下直接返回。
        flag_button = 1;
        button_timer.expires = jiffies + (HZ/50);//20ms定时，定时器按键去抖。
        add_timer(&button_timer);//加入定时器
    }

    return IRQ_HANDLED;
}

static void buttons_timer(unsigned long arg)//定时器处理函数，当设定时间到达后，调用此程序。
{
    pin = gpio_get_value(S5PV210_GPH0(0));//读出中断按键值，保存在pin中。

    if(pin == 0){//若按键按下，再次调用定时器，设置定时时间为100ms.
        button_timer.expires = jiffies + (HZ/10);
        add_timer(&button_timer);
        num_button++;
    }
    else{//若按键已释放，计算按键按下时间num_button*100ms.
        if(num_button >= 50){//若num_button>=50,即按下时间大于5S，将ev_press 置一，唤醒休眠。
            num_button = 0;//清0
            flag_button = 0;//按键未按下。
            ev_press = 1;
            wake_up_interruptible(&button_waitq);
        }
        else{//若按键按下时间不足5S，flag_button置0， num_button 置0.
            flag_button = 0;
            num_button = 0;
            printk(KERN_INFO " timer<5s \n");
        }
    }
}

static int tengyue210_buttons_open(struct inode *inode, struct file *file)
{
    int irq;
    int err = 0;

	//注册中断处理函数
	/*for (i = 0; i <sizeof(buttons)/sizeof(buttons[0]); i++) {
		if (!buttons[i].gpio)
		continue;
		irq = gpio_to_irq(buttons[i].gpio);//将GPIO映射为IRQ中断
		err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_RISING,
		buttons[i].name, (void *)&press_cnt[i]);//注册中断处理函数
		if (err)
		break;
		}

		if (err) {
		i--;
		for (; i >= 0; i--) {
		if (!buttons[i].gpio)
		continue;

		irq = gpio_to_irq(buttons[i].gpio);
		disable_irq(irq);
		free_irq(irq, (void *)&buttons[i]);//释放已注册的中断号
		}

		return -EBUSY;
	}*/

    printk(KERN_INFO " leds opened\n");
    irq = gpio_to_irq(S5PV210_GPH0(0));//将GPIO映射为IRQ中断
    err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_FALLING,
                      "KEY", (void *)&press_cnt[0]);//注册中断处理函数

    if (err) {
        irq = gpio_to_irq(S5PV210_GPH0(0));
        disable_irq(irq);
        free_irq(irq, (void *)&press_cnt[0]);//释放已注册的中断号
    }

    err = gpio_request(S5PV210_GPC1(4), "GPC1_4");//申请GPIO

    if(err)
    {
        printk(KERN_ERR "failed to request GPC1_4 \n");
        return err;
    }

    s3c_gpio_cfgpin(S5PV210_GPC1(4), S3C_GPIO_SFN(1));//设置成输出引脚
    gpio_direction_output(S5PV210_GPC1(4), 1);//设置GPIO方向，若是输出同时设置电平。
//s3c_gpio_setpin(S5PV210_GPC1(4), 1);//设置输出电平

    setup_timer(&button_timer,&buttons_timer,0);
    ev_press = 1;

    return 0;
}

static int tengyue210_buttons_close(struct inode *inode, struct file *file)
{
    int irq;

/*for (i = 0; i < ARRAY_SIZE(buttons); i++) {//ARRAY_SIZE(buttons)=sizeof(buttons)/sizeof(buttons[0])
  if (!buttons[i].gpio)
  continue;
  irq = gpio_to_irq(buttons[i].gpio);
  free_irq(irq, (void *)&buttons[i]);
  }*/

    irq = gpio_to_irq(S5PV210_GPH0(0));
    free_irq(irq,(void *)&press_cnt[0]);
    gpio_free(S5PV210_GPC1(4));

    return 0;
}

static int tengyue210_buttons_read(struct file *filp, char __user *buff,
                                   size_t count, loff_t *offp)
{
    unsigned long err;

    if (!ev_press) {
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
        else
            wait_event_interruptible(button_waitq, ev_press);//休眠
    }

    ev_press = 0;
    err = copy_to_user((void *)buff, (const void *)press_cnt,min(sizeof(press_cnt), count));//将内核空间数据拷贝到用户空间。
    memset((void *)press_cnt,0,sizeof(press_cnt));

    return err ? -EFAULT : min(sizeof(press_cnt), count);
}

static long tengyue210_buttons_ioctl(
    struct inode *inode,
    struct file *file,
    unsigned int cmd,
    unsigned long arg)
{
    if (arg > 2)
    {
        return -EINVAL;
    }

    switch(cmd)
    {
    case IOCTL_GPIO_ON:
// 设置指定引脚的输出电平为1
        gpio_direction_output(S5PV210_GPC1(4), 1);
//s3c_gpio_setpin(S5PV210_GPC1(4), 1);
        return 0;

    case IOCTL_GPIO_OFF:
// 设置指定引脚的输出电平为0
        gpio_direction_output(S5PV210_GPC1(4), 0);
//s3c_gpio_setpin(S5PV210_GPC1(4), 0);
        return 0;

    default:
        return -EINVAL;
    }
}

/*使用misc来命名主要是表示该文件目前还没归类好，不知道将它归到哪个方面或者放置在哪个地方比较好，所以暂时用misc。*/
static struct file_operations dev_fops = {
    .owner = THIS_MODULE,
    .open = tengyue210_buttons_open,
    .release = tengyue210_buttons_close,
    .read = tengyue210_buttons_read,
    .ioctl = tengyue210_buttons_ioctl,
};

static struct miscdevice misc = {
    .mino = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &dev_fops,
};

static int __init button_dev_init(void)
{
    int ret;

    ret = misc_register(&misc);//在加载模块时会自动创建设备文件，为主设备号为10的字符设备，会自动创建设备节点，成功返回设备的主设备号
    printk(DEVICE_NAME"\tinitialized\n");
    return ret;
}

static void __exit button_dev_exit(void)
{
    misc_deregister(&misc);//卸载设备文件
}

module_init(button_dev_init);
module_exit(button_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("tengyue");



